<?php
//
//	file: inc/tree.class.php
//	author: Pierre ROGE pour GAIA (gaia.fr)
//	begin: 13/02/2008
//	version: 0.0.1 - 13/02/2008
//	license: http://opensource.org/licenses/gpl-license.php GNU General Public License (GPL)
//

if ( !defined('SYS') )
{
	die('Not allowed');
}

class sys_tree extends sys_stdclass
{
	var $data;
	var $table;
	var $type;
	var $legend;
	var $field;

	var $field_id;
	var $field_pid;
	var $field_lid;
	var $field_rid;

	function __construct($api_name)
	{
		parent::__construct($api_name);
		$this->data = false; // data indexed per id
		$this->table = false; // table name
		$this->type = false;
		$this->legend = false;
		$this->field = false; // field prefix

		$this->field_id = false; // field . _id
		$this->field_pid = false; // field . _pid
		$this->field_lid = false; // field . _lid
		$this->field_rid = false; // field . _rid
	}

	function __destruct()
	{
		unset($this->field_rid, $this->field_lid, $this->field_id, $this->field_pid);

		unset($this->field);
		unset($this->legend);
		unset($this->type);
		unset($this->table);
		unset($this->data);
		parent::__destruct();
	}

	function set()
	{
		$this->field_id = $this->field . '_id';
		$this->field_pid = $this->field . '_pid';
		$this->field_lid = $this->field . '_lid';
		$this->field_rid = $this->field . '_rid';
	}

	function get_root()
	{
		$db = &$GLOBALS[$this->api_name]->db;
		$sql = 'SELECT COUNT(*) AS count_rows
					FROM ' . $db->table($this->table);
		$result = $db->query($sql, __LINE__, __FILE__);
		$rid = ($row = $db->fetch($result)) ? intval($row['count_rows']) * 2 + 1 : 1;
		$db->free($result);

		return array(0 => array(
			$this->field_id => 0,
			$this->field_lid => 0,
			$this->field_rid => $rid,
		));
	}

	// read the whole tree
	function read()
	{
		$db = &$GLOBALS[$this->api_name]->db;

		$this->data = array(0 => array());
		$sql = 'SELECT *
					FROM ' . $db->table($this->table)  . '
					ORDER BY ' . $this->field_lid;
		$result = $db->query($sql, __LINE__, __FILE__);
		while ( ($row = $db->fetch($result)) )
		{
			$this->data[ (int) $row[$this->field_id] ] = $row;
		}
		$db->free($result);
		$this->data[0] = array(
			$this->field_id => 0,
			$this->field_lid => 0,
			$this->field_rid => (count($this->data) - 1) * 2 + 1,
		);
		return true;
	}

	// retrieve opened branch
	function read_open($id=false)
	{
		$api = &$GLOBALS[$this->api_name];
		$db = &$api->db;

		// we start from a point, not from root
		$lid = $rid = $pid = false;
		if ( $id )
		{
			$sql = 'SELECT ' . implode(', ', array($this->field_id, $this->field_lid, $this->field_rid, $this->field_pid)) . '
						FROM ' . $db->table($this->table) . '
						WHERE ' . $this->field_id . ' = ' . intval($id);
			$result = $db->query($sql, __LINE__, __FILE__);
			$id = false;
			if ( ($row = $db->fetch($result)) )
			{
				$id = (int) $row[$this->field_id];
				$lid = (int) $row[$this->field_lid];
				$rid = (int) $row[$this->field_rid];
				$pid = (int) $row[$this->field_pid];
			}
			$db->free($result);
		}

		// read data
		$this->data = $this->get_root();
		$sql = 'SELECT t.*
					FROM ' . $db->table($this->table) . ' t
						LEFT JOIN ' . $db->table('users_tree') . ' ut
							ON ut.user_id = ' . ($api->user->data ? intval($api->user->data['user_id']) : 0) . '
								AND ut.tree_type = ' . $db->escape($this->type) . '
								AND ut.tree_id = t.' . $this->field_pid . '
					WHERE ' . ($id ? ($pid ? '(t.' . $this->field_lid . ' <= ' . intval($lid) . ' AND t.' . $this->field_rid . ' >= ' . intval($rid) . ')' : 't.' . $this->field_id . ' = ' . intval($id)) . '
						OR (ut.tree_id IS NOT NULL AND t.' . $this->field_lid . ' BETWEEN ' . intval($lid) . ' AND ' . intval($rid) . ')' : 't.' . $this->field_pid . ' = 0
						OR ut.tree_id IS NOT NULL') . '
					ORDER BY ' . $this->field_lid;
		$result = $db->query($sql, __LINE__, __FILE__);
		while ( ($row = $db->fetch($result)) )
		{
			$this->data[ (int) $row[$this->field_id] ] = $row;
		}
		$db->free($result);
		return true;
	}

	// read one node
	function read_item($id)
	{
		$db = &$GLOBALS[$this->api_name]->db;

		if ( isset($this->data[$id]) )
		{
			return true;
		}
		$sql = 'SELECT *
					FROM ' . $db->table($this->table) . '
					WHERE ' . $this->field_id . ' = ' . intval($id);
		$result = $db->query($sql, __LINE__, __FILE__);
		$item = ($row = $db->fetch($result)) ? $row : false;
		$db->free($result);
		if ( $item )
		{
			$this->data[ (int) $row[$this->field_id] ] = $row;
		}
		return $item !== false;
	}

	// read parent nodes: this will reset $this->data
	function read_ancestors($id)
	{
		$db = &$GLOBALS[$this->api_name]->db;

		if ( !$this->read_item($id) )
		{
			return false;
		}

		// reset
		$item = $this->data[$id];
		$this->data = $this->get_root();

		// read if we are not at top
		if ( $item[$this->field_lid] > 1 )
		{
			$sql = 'SELECT *
						FROM ' . $db->table($this->table) . '
						WHERE ' . $this->field_lid . ' < ' . intval($item[$this->field_lid]) . ' AND ' . $this->field_rid . ' > ' . intval($item[$this->field_rid]) . '
						ORDER BY ' . $this->field_lid;
			$result = $db->query($sql, __LINE__, __FILE__);
			while ( ($row = $db->fetch($result)) )
			{
				$this->data[ (int) $row[$this->field_id] ] = $row;
			}
			$db->free($result);
		}

		// re-add the node
		$row = $item;
		$this->data[ (int) $row[$this->field_id] ] = $row;
		return true;
	}

	// read child nodes
	function read_childs($id)
	{
		$db = &$GLOBALS[$this->api_name]->db;

		if ( !$id )
		{
			return $this->read();
		}
		if ( !$this->read_item($id) )
		{
			return false;
		}

		// read if we are not at end of branch
		if ( $this->data[$id][$this->field_rid] > $this->data[$id][$this->field_lid] + 1 )
		{
			$sql = 'SELECT *
						FROM ' . $db->table($this->table) . '
						WHERE ' . $this->field_lid . ' BETWEEN ' . intval($this->data[$id][$this->field_lid] + 1) . ' AND ' . intval($this->data[$id][$this->field_rid]) . '
						ORDER BY ' . $this->field_lid;
			$result = $db->query($sql, __LINE__, __FILE__);
			while ( ($row = $db->fetch($result)) )
			{
				$this->data[ (int) $row[$this->field_id] ] = $row;
			}
			$db->free($result);
		}
	}

	// read the direct childs
	function read_direct_childs($id)
	{
		$db = &$GLOBALS[$this->api_name]->db;

		if ( $id && !$this->read_item($id) )
		{
			return false;
		}

		// read if we are not at end of branch
		if ( !$id || ($this->data[$id][$this->field_rid] > $this->data[$id][$this->field_lid] + 1) )
		{
			if ( !$id )
			{
				$this->data = array(0 => array());
			}
			$sql = 'SELECT *
						FROM ' . $db->table($this->table) . '
						WHERE ' . $this->field_pid . ' = ' . intval($id) . '
						ORDER BY ' . $this->field_pid . ', ' . $this->field_lid;
			$result = $db->query($sql, __LINE__, __FILE__);
			while ( ($row = $db->fetch($result)) )
			{
				$this->data[ (int) $row[$this->field_id] ] = $row;
			}
			$db->free($result);
		}
	}

	// open/close a branch
	function open_close_branch($id, $close=false)
	{
		$api = &$GLOBALS[$this->api_name];
		$db = &$api->db;
		$user = &$api->user;

		// no id or no user or id not found: end there
		if ( !$id || !$user->data || !$this->read_item($id) )
		{
			return false;
		}

		// get lid/rid
		$lid = (int) $this->data[$id][$this->field_lid];
		$rid = (int) $this->data[$id][$this->field_rid];

		// get ancestors
		$parent_ids = array($id);
		$sql = 'SELECT ' . $this->field_id . '
					FROM ' . $db->table($this->table) . '
					WHERE ' . $this->field_lid . ' < ' . $lid . ' AND ' . $this->field_rid . ' > ' . $rid;
		$result = $db->query($sql, __LINE__, __FILE__);
		while ( ($row = $db->fetch($result)) )
		{
			$parent_ids[] = (int) $row[$this->field_id];
		}
		$db->free($result);
		$sql_wheres = array('tree_id ' . (count($parent_ids) == 1 ? ' = ' . intval($parent_ids[0]) : ' IN(' . implode(', ', $parent_ids) . ')'));

		// childs
		if ( $rid > $lid + 1 )
		{
			$sql_wheres[] = 'tree_id IN(' . $db->sub_query($this->field_id, '
						SELECT ' . $this->field_id . '
							FROM ' . $db->table($this->table) . '
							WHERE ' . $this->field_lid . ' BETWEEN ' . intval($lid + 1) . ' AND ' . intval($rid) . '
						', __LINE__, __FILE__) . ')';
		}

		// delete this item plus its ancestors (plus its childs opened if we close it)
		$sql = 'DELETE FROM ' . $db->table('users_tree') . '
					WHERE user_id = ' . intval($api->user->data['user_id']) . '
						AND tree_type = ' . $db->escape($this->type) . '
						AND (
							' . implode('
							OR ', $sql_wheres) . '
						)';
		$db->query($sql, __LINE__, __FILE__);

		// we except the id in the re-insertion if we close it or if it has no childs
		$except_id = $close || ($rid == $lid + 1) ? $id : false;

		// re-insert ancestors plus this item id
		$fields = array();
		foreach ( $parent_ids as $tree_id )
		{
			if ( $tree_id !== $except_id )
			{
				$fields[] = array(
					'user_id' => intval($api->user->data['user_id']),
					'tree_type' => $this->type,
					'tree_id' => $tree_id,
				);
			}
		}
		if ( $fields )
		{
			$sql = 'INSERT INTO ' . $db->table('users_tree') . '
						(' . $db->fields('fields', $fields) . ') VALUES(' . $db->fields('values', $fields) . ')';
			$db->query($sql, __LINE__, __FILE__);
		}
		return true;
	}

	//
	// the following methods require the tree to be reread
	//

	// insert a node in the tree
	function insert($fields)
	{
		$db = &$GLOBALS[$this->api_name]->db;

		// make some room at the insert point
		$sql = 'UPDATE ' . $db->table($this->table) . '
					SET ' . $this->field_lid . ' = ' . $this->field_lid . ' + 2, ' . $this->field_rid . ' = ' . $this->field_rid . ' + 2
					WHERE ' . $this->field_lid . ' >= ' . intval($fields[$this->field_lid]);
		$db->query($sql, __LINE__, __FILE__);

		// adjust ancestors
		$sql = 'UPDATE ' . $db->table($this->table) . '
					SET ' . $this->field_rid . ' = ' . $this->field_rid . ' + 2
					WHERE ' . $this->field_lid . ' < ' . intval($fields[$this->field_lid]) . '
						AND ' . $this->field_rid . ' >= ' . intval($fields[$this->field_lid]);
		$db->query($sql, __LINE__, __FILE__);

		// create the node
		$sql = 'INSERT INTO ' . $db->table($this->table) . '
					(' . $db->fields('fields', $fields) . ') VALUES(' . $db->fields('values', $fields) . ')';
		$db->query($sql, __LINE__, __FILE__);
		$id = $db->next_id();
		$this->data[$id] = array($this->field_id => $id) + $fields;
		$this->open_close_branch($id);

		// copy management auths
		$this->copy_managers($id);

		return $id;
	}

	// update
	function update($id, $fields, $silent=false)
	{
		$db = &$GLOBALS[$this->api_name]->db;

		// do we need to move the branch ?
		$lid = $this->data[$id][$this->field_lid];
		$rid = $this->data[$id][$this->field_rid];
		if ( ($fields[$this->field_lid] != $lid) || ($fields[$this->field_rid] != $rid) )
		{
			$nodes = $rid - $lid + 1; // actually the number of nodes - including this one - would be: (rid - lid + 1) / 2, but this is a convenient notation :)

			// make some room at the insert point
			$sql = 'UPDATE ' . $db->table($this->table) . '
						SET ' . $this->field_lid . ' = ' . $this->field_lid . ' + ' . intval($nodes) . ', ' . $this->field_rid . ' = ' . $this->field_rid . ' + ' . intval($nodes) . '
						WHERE ' . $this->field_lid . ' >= ' . intval($fields[$this->field_lid]);
			$db->query($sql, __LINE__, __FILE__);

			// adjust ancestors
			$sql = 'UPDATE ' . $db->table($this->table) . '
						SET ' . $this->field_rid . ' = ' . $this->field_rid . ' + ' . intval($nodes) . '
						WHERE ' . $this->field_lid . ' < ' . intval($fields[$this->field_lid]) . '
							AND ' . $this->field_rid . ' >= ' . intval($fields[$this->field_lid]);
			$db->query($sql, __LINE__, __FILE__);

			// the block to move is set after the target point
			if ( $lid >= $fields[$this->field_lid] )
			{
				$lid += $nodes;
				$rid += $nodes;
			}

			// move the block
			$delta = $fields[$this->field_lid] - $lid;
			$sql = 'UPDATE ' . $db->table($this->table) . '
						SET ' . $this->field_lid . ' = ' . $this->field_lid . ($delta < 0 ? ' - ' : ' + ') . abs($delta) . ', ' . $this->field_rid . ' = ' . $this->field_rid . ($delta < 0 ? ' - ' : ' + ') . abs($delta) . '
						WHERE ' . $this->field_lid . ' BETWEEN ' . intval($lid) . ' AND ' . intval($rid);
			$db->query($sql, __LINE__, __FILE__);

			// remove the gap generated at $lid -> $rid place
			$sql = 'UPDATE ' . $db->table($this->table) . '
						SET ' . $this->field_lid . ' = ' . $this->field_lid . ' - ' . intval($nodes) . ', ' . $this->field_rid . ' = ' . $this->field_rid . ' - ' . intval($nodes) . '
						WHERE ' . $this->field_lid . ' >= ' . intval($lid);
			$db->query($sql, __LINE__, __FILE__);

			// adjust ancestors
			$sql = 'UPDATE ' . $db->table($this->table) . '
						SET ' . $this->field_rid . ' = ' . $this->field_rid . ' - ' . intval($nodes) . '
						WHERE ' . $this->field_lid . ' < ' . intval($lid) . '
							AND ' . $this->field_rid . ' >= ' . intval($lid);
			$db->query($sql, __LINE__, __FILE__);
		}

		// update the node
		$this->data[$id] = array_merge($this->data[$id], $fields);
		unset($fields[$this->field_lid], $fields[$this->field_rid]);
		if ( $fields )
		{
			$sql = 'UPDATE ' . $db->table($this->table) . '
						SET ' . $db->fields('update', $fields) . '
						WHERE ' . $this->field_id . ' = ' . intval($id);
			$db->query($sql, __LINE__, __FILE__);
		}
		if ( !$silent )
		{
			$this->open_close_branch($id);
		}

		return true;
	}

	// delete a node (and its sub-branch)
	function delete($id)
	{
		$db = &$GLOBALS[$this->api_name]->db;

		$pid = $this->data[$id][$this->field_pid];
		$lid = $this->data[$id][$this->field_lid];
		$rid = $this->data[$id][$this->field_rid];
		$nodes = $rid - $lid + 1;

		$sub_query = $db->sub_query($this->field_id, '
						SELECT ' . $this->field_id . '
							FROM ' . $db->table($this->table) . '
							WHERE ' . $this->field_lid . ' BETWEEN ' . intval($lid) . ' AND ' . intval($rid) . '
						', __LINE__, __FILE__);

		// delete dependant data
		if ( method_exists($this, 'delete_cascading') )
		{
			$this->delete_cascading($sub_query);
		}

		// delete all sub-nodes
		$sql = 'DELETE FROM ' . $db->table($this->table) . '
					WHERE ' . $this->field_lid . ' BETWEEN ' . intval($lid) . ' AND ' . intval($rid);
		$db->query($sql, __LINE__, __FILE__);

		// remove the gap generated at $lid -> $rid place
		$sql = 'UPDATE ' . $db->table($this->table) . '
					SET ' . $this->field_lid . ' = ' . $this->field_lid . ' - ' . intval($nodes) . ', ' . $this->field_rid . ' = ' . $this->field_rid . ' - ' . intval($nodes) . '
					WHERE ' . $this->field_lid . ' >= ' . intval($lid);
		$db->query($sql, __LINE__, __FILE__);

		// adjust ancestors
		$sql = 'UPDATE ' . $db->table($this->table) . '
					SET ' . $this->field_rid . ' = ' . $this->field_rid . ' - ' . intval($nodes) . '
					WHERE ' . $this->field_lid . ' < ' . intval($lid) . '
						AND ' . $this->field_rid . ' >= ' . intval($lid);
		$db->query($sql, __LINE__, __FILE__);

		$this->open_close_branch($pid);
	}

	function move($id, $up=false)
	{
		$db = &$GLOBALS[$this->api_name]->db;

		// read the item
		if ( !$this->read_item($id) )
		{
			return false;
		}

		// the ids
		$moved_id = false;
		$after_id = false;

		// move down: we move the asked item after his next brother
		if ( !$up )
		{
			// get the next brother
			$sql = 'SELECT ' . implode(', ', array($this->field_id, $this->field_lid, $this->field_rid, $this->field_pid)) . '
						FROM ' . $db->table($this->table) . '
						WHERE ' . $this->field_lid . ' = ' . intval($this->data[$id][$this->field_rid] + 1);
			$result = $db->query($sql, __LINE__, __FILE__);
			$found = ($row = $db->fetch($result)) ? true : false;
			$db->free($result);
			if ( !$found )
			{
				return false;
			}
			$moved_id = $id;
			$after_id = (int) $row[$this->field_id];
			$this->data[$after_id] = $row;
		}
		// move up: we move the previous brother after the asked item
		else
		{
			// get the previous brother
			$sql = 'SELECT ' . implode(', ', array($this->field_id, $this->field_lid, $this->field_rid, $this->field_pid)) . '
						FROM ' . $db->table($this->table) . '
						WHERE ' . $this->field_rid . ' = ' . intval($this->data[$id][$this->field_lid] - 1);
			$result = $db->query($sql, __LINE__, __FILE__);
			$found = ($row = $db->fetch($result)) ? true : false;
			$db->free($result);
			if ( !$found )
			{
				return false;
			}
			$moved_id = (int) $row[$this->field_id];
			$after_id = $id;
			$this->data[$moved_id] = $row;
		}

		// move the branch
		$fields = array(
			$this->field_lid => $this->data[$after_id][$this->field_rid] + 1,
			$this->field_rid => $this->data[$after_id][$this->field_rid] + 1 + ($this->data[$moved_id][$this->field_rid] - $this->data[$moved_id][$this->field_lid]),
		);
		$this->update($moved_id, $fields, true);
	}

	function attach($id, $up=false)
	{
		$db = &$GLOBALS[$this->api_name]->db;

		// read the item
		if ( !$this->read_item($id) )
		{
			return false;
		}
		if ( ($up && !$this->data[$id][$this->field_pid]) )
		{
			return false;
		}
		// detach from current parent and attach to grand'pa
		if ( $up )
		{
			$brother_id = $this->data[$id][$this->field_pid];

			// we need the parent
			if ( !$this->read_item($brother_id) )
			{
				return false;
			}
			$pid = $this->data[$brother_id][$this->field_pid];
			$lid = $this->data[$brother_id][$this->field_rid] + 1;
		}
		// we make the direct oldest brother the new parent
		else
		{
			$sql = 'SELECT ' . implode(', ', array($this->field_id, $this->field_lid, $this->field_rid, $this->field_pid)) . '
						FROM ' . $db->table($this->table) . '
						WHERE ' . $this->field_rid . ' = ' . intval($this->data[$id][$this->field_lid] - 1);
			$result = $db->query($sql, __LINE__, __FILE__);
			$found = ($row = $db->fetch($result)) ? true : false;
			$db->free($result);
			if ( !$found )
			{
				return false;
			}
			$pid = $row[$this->field_id];
			$lid = $row[$this->field_rid];
		}

		// update
		$fields = array(
			$this->field_pid => $pid,
			$this->field_lid => $lid,
			$this->field_rid => $lid + ($this->data[$id][$this->field_rid] - $this->data[$id][$this->field_lid]),
		);
		$this->update($id, $fields, true);
		if ( !$up )
		{
			$this->open_close_branch($pid);
		}
	}

	//
	// work on item
	//

	// return an option for the lists
	function get_option($id)
	{
	}

	function support_auths($id)
	{
		return $id && isset($this->data[$id]);
	}

	//
	// deal with manager auths
	//
	function copy_managers($id)
	{
		$db = &$GLOBALS[$this->api_name]->db;

		if ( !$id || !isset($this->data[$id]) || !$this->data[$this->field_pid] || !($auth_names = $this->get_management_auths()) )
		{
			return false;
		}
		// read the parent managers auths
		$auths = array();
		$sql = 'SELECT *
					FROM ' . $db->table('group_auths') . '
					WHERE auth_type = ' . $db->escape((string) $this->type) . '
						AND obj_id = ' . intval($this->data[$this->field_pid]) . '
						AND group_id IN(' . $db->sub_query('group_id', '
							SELECT DISTINCT group_id
								FROM ' . $db->table('groups_auths') . '
								WHERE auth_type = ' . $db->escape((string) $this->type) . '
									AND obj_id = ' . intval($this->data[$this->field_pid]) . '
									AND auth_name ' . (count($auth_names) == 1 ? $db->escape($auth_names[0]) : implode(', ', array_map(array(&$db, 'escape'), $auth_names))) . '
							', __LINE__, __FILE__) . ')';
		$result = $db->query($sql, __LINE__, __FILE__);
		while ( ($row = $db->fetch($result)) )
		{
			if ( !isset($auths[$group_id]) )
			{
				$auths[$group_id] = array();
			}
			$auths[$group_id] += array($row['auth_name'] => $row['auth_value']);
		}
		$db->free($result);

		// create them for the new id
		if ( $auths)
		{
			$field_names = array('group_id', 'obj_id', 'auth_type', 'auth_name', 'auth_value');
			$fields = array();
			$count_fields = 0;
			foreach ( $auths as $group_id => $row )
			{
				unset($auths[$group_id]);
				$fields[] = array(
					(int) $group_id,
					(int) $id,
					(string) $this->type,
					(string) $row['auth_name'],
					(int) $row['auth_value'],
				);
				$count_fields++;
				if ( $count_fields > $db->per_turn )
				{
					$sql = 'INSERT INTO ' . $db->table('groups_auths') . '
								(' . $db->fields('fields', $field_names) . ') VALUES(' . $db->fields('values', $fields) . ')';
					$fields = array();
					$count_fields = 0;
					$db->query($sql, __LINE__, __FILE__);
					unset($sql);
				}
			}
			if ( $fields )
			{
				$sql = 'INSERT INTO ' . $db->table('groups_auths') . '
							(' . $db->fields('fields', $field_names) . ') VALUES(' . $db->fields('values', $fields) . ')';
				unset($fields);
				$db->query($sql, __LINE__, __FILE__);
				unset($sql);
			}
		}
	}

	function get_management_auths()
	{
		return array('manage', 'authorize');
	}

	//
	// the following methods require the full tree in $this->data
	//

	// get lists for the tree_parent field
	function get_field_parent_lists($cur_id, $main_id, &$parents, &$orders)
	{
		$user = &$GLOBALS[$this->api_name]->user;

		$parents = $orders = array();
		$stack = array();
		$keys = array_keys($this->data);
		$count_keys = count($keys);
		for ( $i = 0; $i < $count_keys; $i++ )
		{
			// if rid > parent.rid, we have finished with this parent, so remove it from the stack. nb: $count_stack == 1 <=> only root is stacked, without rid
			while ( ($count_stack = count($stack)) && ($count_stack > 1) && ($this->data[ $keys[$i] ][$this->field_rid] > $this->data[ $stack[ ($count_stack - 1) ] ][$this->field_rid]) )
			{
				array_pop($stack);
			}
			$parent_id = $count_stack ? $stack[ ($count_stack - 1) ] : 0;

			// "attached to" list
			if ( !$cur_id || ($cur_id != $keys[$i]) )
			{
				$parents[ $keys[$i] ] = $this->get_option($keys[$i]) + ($count_stack ? array('inc' => $count_stack) : array());
				if ( !$this->allowed_as_parent($keys[$i], $cur_id) )
				{
					$parents[ $keys[$i] ]['disabled'] = true;
				}
			}

			// "order" list
			if ( ($keys[$i] == $main_id) || ($parent_id == $main_id) )
			{
				$orders[ $keys[$i] ] = $this->get_option($keys[$i]) + ($keys[$i] != $main_id ? array('inc' => 1) : array());
			}

			// store id in the parent stack
			$stack[] = $keys[$i];

			// exclude the branch
			if ( $cur_id && ($keys[$i] == $cur_id) )
			{
				$i += intval(($this->data[ $keys[$i] ][$this->field_rid] - $this->data[ $keys[$i] ][$this->field_lid] - 1) / 2);
			}
		}
	}

	// check if the id can be used as parent of cur_id
	function allowed_as_parent($id, $cur_id)
	{
		return true;
	}

	// check if the id can be used for the action
	function allowed($id, $action=false)
	{
		return ($action == 'create') || $id ? true : false;
	}
}

?>